<?php
namespace Sdk\Amazon;

use Exception;
use GuzzleHttp\Client;

class callClient
{
    protected string $url;
    protected string $site;
    protected string $proxy;
    protected string $region;
    protected string $endpoint;
    public string $marketplaceId;
    protected $accessKey;
    protected $secretKey;
    protected $accessToken;
    protected $sessionToken;
    protected $lastHttpResponse = null;

    public function __construct($config = [])
    {
        $this->site = $config['site'];
        $this->proxy = $config['proxy'] ?? '';
        $this->accessKey = $config['access_key'];
        $this->secretKey = $config['secret_key'];
        $this->accessToken = $config['access_token'];
        $this->sessionToken = $config['session_token'];
        $this->configure();
    }

    /**
     * 发送亚马逊的请求
     *
     * @param string $uri
     * @param array $requestOptions
     * @param string $method
     * @param array $headers
     * $param array $proxy
     * @return array
     * @throws Exception
     */
    public function send($uri, $requestOptions = [])
    {
        $client = new Client();
        $url = 'https://' . $this->endpoint . $uri;
        $requestOptions = $this->prepareRequestOptions($uri, $requestOptions);

        $this->lastHttpResponse = null;
        $method = $requestOptions['method'];
        unset($requestOptions['method']);
        try {
            if (!empty($this->proxy)) {
                $formParams = $this->proxyParams($method, $url, $requestOptions);
                $response = $client->request($method, $this->proxy, ['form_params' => $formParams]);
            } else {
                $response = $client->request($method, $url, $requestOptions);
            }
            $this->lastHttpResponse = $response;
            return json_decode($response->getBody(), true);
        } catch (\GuzzleHttp\Exception\ClientException $e) {
            $this->lastHttpResponse = $e->getResponse()->getBody()->getContents();
            throw $e;
        }
    }

    /**
     * 描述 : 匹配SP-API端点和市场ID
     * 返回 : {
     *      "region" : "区域",
     *      "endpoint" : "端点",
     *      "marketplaceId" : "市场ID",
     * }
     * @throws Exception
     */
    public function configure()
    {
        $regionMap = [
            'us-east-1' => ['CA', 'US', 'MX', 'BR'],
            'eu-west-1' => ['ES', 'UK', 'FR', 'NL', 'DE', 'IT', 'SE', 'TR', 'AE', 'IN', 'PL', 'SA'],
            'us-west-2' => ['SG', 'AU', 'JP']
        ];
        $endpointMap = [
            'us-west-2' => 'sellingpartnerapi-fe.amazon.com',
            'us-east-1' => 'sellingpartnerapi-na.amazon.com',
            'eu-west-1' => 'sellingpartnerapi-eu.amazon.com',
        ];
        $marketplaceIdMap = [
            'CA' => 'A2EUQ1WTGCTBG2',
            'US' => 'ATVPDKIKX0DER',
            'MX' => 'A1AM78C64UM0Y8',
            'BR' => 'A2Q3Y263D00KWC',
            'ES' => 'A1RKKUPIHCS9HS',
            'UK' => 'A1F83G8C2ARO7P',
            'FR' => 'A13V1IB3VIYZZH',
            'BE' => 'AMEN7PMS3EDWL',
            'NL' => 'A1805IZSGTT6HS',
            'DE' => 'A1PA6795UKMFR9',
            'IT' => 'APJ6JRA9NG5V4',
            'SE' => 'A2NODRKZP88ZB9',
            'ZA' => 'AE08WJ6YKNBMC',
            'PL' => 'A1C3SOZRARQ6R3',
            'EG' => 'ARBP9OOSHTCHU',
            'SA' => 'A17E79C6D8DWNP',
            'TR' => 'A33AVAJ2PDY3EV',
            'AE' => 'A2VIGQ35RCS4UG',
            'IN' => 'A21TJRUUN4KGV',
            'SG' => 'A19VAU5U5O7RUS',
            'AU' => 'A39IBJ37TRP1C6',
            'JP' => 'A1VC38T7YXB528',
        ];
        foreach ($regionMap as $region => $siteMap) {
            if (in_array($this->site, $siteMap)) {
                $this->region = $region;
                break;
            }
        }
        //检查是否为空
        $requiredKeys = [
            'Access key' => $this->accessKey,
            'Secret key' => $this->secretKey,
            'Region key' => $region,
            'Access token' => $this->accessToken,
            'Session token' => $this->sessionToken
        ];
        foreach ($requiredKeys as $key => $value) {
            if (is_null($value)) {
                throw new \Exception("$key is required");
            }
        }

        $this->endpoint = $endpointMap[$this->region] ?? '';
        if (empty($this->endpoint)) {
            throw new Exception('endpoint not found.');
        }
        $this->marketplaceId = $marketplaceIdMap[$this->site] ?? '';
        if (empty($this->marketplaceId)) {
            throw new Exception('marketplaceId not found.');
        }
    }

    // 代理转发请求参数
    private function proxyParams($method, $url, $requestOptions)
    {
        $headers = array_map(function($key, $value) {
            return "$key:$value";
        }, array_keys($requestOptions['headers']), $requestOptions['headers']);

        $formParams = [
            'post_url' => $url,
            'post_header' => $headers,
            'post_method' => strtolower($method),
        ];

        if ($method == 'GET') {
            $query = http_build_query($requestOptions['query']);
            $formParams['post_url'] .= '?' . $query;
        } else {
            $formParams['data_type'] = 'json';
            $formParams['data'] = json_encode($requestOptions['json']);
        }

        return $formParams;
    }

    private function prepareRequestOptions($uri, $requestOptions)
    {
        $requestOptions['headers'] = $requestOptions['headers'] ?? [];
        $requestOptions['headers']['accept'] = 'application/json';
        $requestOptions['headers']['Content-Type'] = 'application/json; charset=utf-8';
        $requestOptions['headers'] = $this->normalizeHeaders($requestOptions['headers']);

        //Prepare for signing
        $signOptions = [
            'service' => 'execute-api',
            'uri' =>  $uri,
            'method' => $requestOptions['method']
        ];

        if (isset($requestOptions['query'])) {
            $query = $requestOptions['query'];
            ksort($query);
            $signOptions['query_string'] =  \GuzzleHttp\Psr7\build_query($query);
        }

        if (isset($requestOptions['form_params'])) {
            ksort($requestOptions['form_params']);
            $signOptions['payload'] = \GuzzleHttp\Psr7\build_query($requestOptions['form_params']);
        }

        if (isset($requestOptions['json'])) {
            ksort($requestOptions['json']);
            $signOptions['payload'] = json_encode($requestOptions['json']);
        }

        //Sign
        return $this->sign($requestOptions, $signOptions);
    }

    private function normalizeHeaders($headers)
    {
        return array_combine(
            array_map(fn($header) => strtolower($header), array_keys($headers)),
            $headers
        );
    }

    /**
     * @throws \Exception
     */
    public function sign($request, $signOptions)
    {
        //required
        $service = 'execute-api';
        $method = $signOptions['method'];
        $userAgent = 'spapi_client';
        $queryString = $signOptions['query_string'] ?? '';
        $data = $signOptions['payload'] ?? [];
        $uri = $signOptions['uri'] ?? '';

        $terminationString = 'aws4_request';
        $algorithm = 'AWS4-HMAC-SHA256';
        $amzdate = gmdate('Ymd\THis\Z');
        $date = substr($amzdate, 0, 8);
        //Prepare payload hash
        if (is_array($data)) {
            $param = json_encode($data);
            if ($param == "[]") {
                $requestPayload = "";
            } else {
                $requestPayload = $param;
            }
        } else {
            $requestPayload = $data;
        }
        $hashedPayload = hash('sha256', $requestPayload);
        //Compute Canonical Headers
        $canonicalHeaders = [
            'host' => $this->endpoint,
            'user-agent' => $userAgent,
        ];
        if (!is_null($this->accessToken)) {
            $canonicalHeaders['x-amz-access-token'] = $this->accessToken;
        }
        $canonicalHeaders['x-amz-date'] = $amzdate;
        if (!is_null($this->sessionToken)) {
            $canonicalHeaders['x-amz-security-token'] = $this->sessionToken;
        }
        $canonicalHeadersStr = '';
        foreach($canonicalHeaders as $h => $v) {
            $canonicalHeadersStr .= $h . ':' . $v . "\n";
        }
        $signedHeadersStr = join(';' , array_keys($canonicalHeaders));
        //Prepare credentials scope
        $credentialScope = $date . '/' . $this->region . '/' . $service . '/' . $terminationString;
        //prepare canonical request
        $canonicalRequest = $method . "\n" . $uri . "\n" . $queryString . "\n" . $canonicalHeadersStr . "\n" . $signedHeadersStr . "\n" . $hashedPayload;
        //Prepare the signature payload
        $stringToSign = $algorithm . "\n" . $amzdate . "\n" . $credentialScope . "\n" . hash('sha256', $canonicalRequest);
        //Prepare lockers
        $kSecret = "AWS4" . $this->secretKey;
        $kDate = hash_hmac('sha256', $date, $kSecret, true);
        $kRegion = hash_hmac('sha256', $this->region, $kDate, true);
        $kService = hash_hmac('sha256', $service, $kRegion, true);
        $kSigning = hash_hmac('sha256', $terminationString, $kService, true);
        //Compute the signature
        $signature = trim(hash_hmac('sha256', $stringToSign, $kSigning)); // Without fourth parameter passed as true, returns lowercase hexits as called for by docs
        $authorizationHeader = $algorithm . " Credential={$this->accessKey}/{$credentialScope}, SignedHeaders={$signedHeadersStr}, Signature={$signature}";

        $headers = array_merge($canonicalHeaders, [
            "Authorization" => $authorizationHeader,
        ]);

        $request['headers'] = array_merge($request['headers'], $headers);

        return $request;
    }
}
